﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content;
using UtilityLibrary;
using MapLibrary;
using FarseerPhysics.Controllers;
using FarseerPhysics.Common.ConvexHull;
using FarseerPhysics.Dynamics;
using FarseerPhysics;
using FarseerPhysics.Collision.Shapes;
using FarseerPhysics.Common;
using FarseerPhysics.Dynamics.Contacts;
using MapLibrary.DataLoading;
using AI;
using FarseerPhysics.Factories;
using MapLibrary.Utility;
using MapLibrary.Players.CollisionResponse;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework.Audio;

namespace Animation
{
    /// <summary>
    /// A Sprite is an ITransformable object that has a Body and SpriteType.  Therefore
    /// it is an animated object that physics can act upon.
    /// 
    /// Sprites need a context in order to be created.  In other words, they cannot be
    /// made without a reference to World (from Farseer physics).  You should only create
    /// Sprites by using the SpriteFactory.  This handles all of the physics setup for
    /// Sprites.
    /// </summary>
    public class Sprite : ITransformable, IPoolable
    {
        protected delegate void InitializationAction();
        public float MAX_VELOCITY_X = ConvertUnits.ToSimUnits(400);
        public float MAX_VELOCITY_Y = ConvertUnits.ToSimUnits(500);
        private VelocityLimitController controller;

        public VelocityLimitController Controller
        {
            get
            {
                return controller;
            }
            set
            {
                controller = value;
            }
        }

        /// <summary>
        /// The position of the Sprite, and the underlying Body.
        /// </summary>
        public Vector2 Position 
        {
            get
            {
                return GetPosition();
            }
            set
            {
                SetPosition(value);
            }
        }

        /// <summary>
        /// The rotation for the Sprite, and the underlying Body.
        /// </summary>
        public float Rotation 
        {
            get
            {
                return GetRotation();
            }

            set
            {
                SetRotation(value);
            }
        }

        /// <summary>
        /// Sets the Scale for the Sprite.
        /// </summary>
        public Vector2 Scale { get; set; }

        /// <summary>
        /// The name of the Bot for this Sprite.
        /// </summary>
        public string BotName { get; set; }

        /// <summary>
        /// Whether or not the Sprite is collidable.
        /// </summary>
        public bool Collidable { get; set; }

        /// <summary>
        /// The name of the SpriteType that describes this Sprite.
        /// </summary>
        public string SpriteTypeName { get; set; }

        protected event InitializationAction OnInitialization;

        protected CollisionResponseManager collisionManager;

        public CollisionResponseManager.CollisionCategory CollisionCategory
        {
            get;
            set;
        }

        /// <summary>
        /// The underlying SpriteType of this Sprite.
        /// </summary>
        [ContentSerializerIgnore]
        public SpriteType Type { 
            get { return type; }
            set { type = value; SpriteTypeName = type.Name; } 
        }

        /// <summary>
        /// The underlying Body that handles physics for the Sprite.
        /// </summary>
        [ContentSerializerIgnore]
        public Body Body
        {
            get;
            set;
        }

        /// <summary>
        /// can the sprite be grabbed with hookshot
        /// </summary>
        [ContentSerializerIgnore]
        public bool Hookable { get; set; }

        [ContentSerializerIgnore]
        public bool Explodeable { get; set; }

        /// <summary>
        /// The texture to draw in this frame.
        /// </summary>
        [ContentSerializerIgnore]
        public Texture2D CurrentTexture { get; set; }

        private SpriteType type;

        /// <summary>
        /// The AI for the sprite
        /// </summary>
        [ContentSerializerIgnore]
        public Bot Bot { get; set; }

        ///// <summary>
        ///// The level containing the sprite
        ///// </summary>
        //[ContentSerializerIgnore]
        //public Level Level { get; set; }
        /// <summary>
        /// Context for the Sprite
        /// </summary>
        public GameWorld World { get; set; }

        public Sprite()
        {
            Scale = Vector2.UnitX + Vector2.UnitY;
            BotName = "testing";
            Collidable = true;
            controller = new VelocityLimitController(MAX_VELOCITY_X, MAX_VELOCITY_Y);
            collisionManager = new CollisionResponseManager();
            Hookable = false;
        }

        /// <summary>
        /// Returns if a point is within the shapes composing
        /// the body of this sprite.
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public bool ContainsPoint(Vector2 point)
        {
            return PointMath.IsPointInBody(Body, point);
        }

        /// <summary>
        /// Step the animation process for the Sprite
        /// </summary>
        public virtual void Update()
        {
            CurrentTexture = Type.GetNextFrame();
            collisionManager.Update();
        }

        public void UpdateAI(Level level, World world)
        {
            if (Bot != null)
            {
                Bot.Update(level, world);
            }
        }

        /// <summary>
        /// Returns the SpriteData for this Sprite.  The SpriteData
        /// is a simply static representation of the current state
        /// of the sprite.  This is used for data loading and saving.
        /// </summary>
        /// <returns></returns>
        public SpriteData GetSpriteData()
        {
            SpriteData d = new SpriteData();
            d.Position = this.Position;
            d.Rotation = this.Rotation;
            d.Scale = this.Scale;
            d.Collidable = this.Collidable;
            d.SpriteTypeName = this.SpriteTypeName;

            return d;
        }

        /// <summary>
        /// Draw the Sprite to the screen.
        /// </summary>
        /// <param name="batch"></param>
        /// <param name="viewport"></param>
        public virtual void Draw(SpriteBatch batch, WorldViewport viewport)
        {
            batch.Draw(
                CurrentTexture,
                viewport.WorldToViewportCoords(GetPosition()),
                null,
                Color.White,
                GetRotation(),
                GetCenter(),
                Scale,
                SpriteEffects.None, 0);
        }

        public virtual void Draw(SpriteBatch batch, WorldViewport viewport, Color color)
        {
            batch.Draw(
                CurrentTexture,
                viewport.WorldToViewportCoords(GetPosition()),
                null,
                color,
                GetRotation(),
                GetCenter(),
                Scale,
                SpriteEffects.None, 0);
        }

        /// <summary>
        /// Draw the Sprite in the LevelEditor.  This is the same as Sprite.Draw, except
        /// it will also draw out the bounding volume.
        /// </summary>
        /// <param name="batch"></param>
        /// <param name="viewport"></param>
        /// <param name="col"></param>
        public void EditorDraw(SpriteBatch batch, WorldViewport viewport, Color col)
        {
            batch.Draw(
                CurrentTexture,
                viewport.WorldToViewportCoords(GetPosition()), 
                null, 
                Color.White, 
                GetRotation(), 
                GetCenter(), 
                Scale, 
                new SpriteEffects(), 0);

            foreach (Fixture f in Body.FixtureList)
            {
                PolygonShape s = (PolygonShape)f.Shape;
                PrimitiveBrush.RenderHull(batch,
                    viewport.WorldToViewportCoords(PointMath.GetTranslation(
                    ConvertUnits.ToDisplayUnits(s.Vertices), Position)));
            }
        }
        
        /// <summary>
        /// Collision handling for Sprite objects.  All subclasses of Sprite
        /// should override this method to provide unique functionality.
        /// </summary>
        /// <param name="f1"></param>
        /// <param name="f2"></param>
        /// <param name="contact"></param>
        /// <returns></returns>
        public virtual bool MyOnCollision(Fixture f1, Fixture f2, Contact contact)
        {
            CollisionResponseManager.CollisionCategory colCat = CollisionResponseManager.CollisionCategory.None;

            Sprite otherSprite = f2.Body.UserData as Sprite;

            if (otherSprite != null)
                colCat = otherSprite.CollisionCategory;

            LevelCollisionRegion collisionRegion = f2.Body.UserData as LevelCollisionRegion;

            if (collisionRegion != null)
                colCat = collisionRegion.CollisionCategory;

            return collisionManager.AddCollisionThisFrame(colCat);
        }

        /// <summary>
        /// Registers the Sprite to the world.  This involves creating and initializing
        /// the Body that will go into world.PhysicsWorld.
        /// </summary>
        /// <param name="world"></param>
        public virtual void RegisterToWorld(GameWorld world)
        {
            World = world;
            CollisionData tempData = World.ContentManager.Load<CollisionData>(type.CollisionData);
            Vertices simHull = ConvertUnits.ToSimUnits(tempData.Hull);
            Path tPath = new Path(simHull);
            Body = BodyFactory.CreateBody(world.PhysicsWorld, ConvertUnits.ToSimUnits(Position));
            tPath.Closed = true;

            float density = tempData.Mass / simHull.GetArea();
            PathManager.ConvertPathToPolygon(tPath, Body, density, tempData.Hull.Count);
            
            Position = Position + GetCenter();
            Body.BodyType = BodyType.Dynamic;
            Body.Friction = tempData.Friction;
            Body.Restitution = tempData.Restitution;
            Body.FixedRotation = tempData.FixedRotation;
            //Body.Mass = tempData.Mass;
            Body.CollidesWith = Category.All;
            Body.CollisionCategories = Category.All;
            Body.OnCollision += MyOnCollision;
            Body.UserData = this;

            Controller.AddBody(Body);
            world.PhysicsWorld.AddController(Controller);

            Collidable = true;

            //((PolygonShape)(Body.FixtureList[0].Shape)).Vertices.Translate(-ConvertUnits.ToSimUnits(GetCenter()));
            for (int i = 0; i < Body.FixtureList.Count; i++)
            {
                ((PolygonShape)(Body.FixtureList[i].Shape)).Vertices.Translate(-ConvertUnits.ToSimUnits(GetCenter()));
            }

            if(OnInitialization != null)
                OnInitialization();
        }

        public virtual void DeregisterFromWorld(GameWorld world)
        {
            world.PhysicsWorld.RemoveController(Controller);
            Controller.RemoveBody(Body);
            world.PhysicsWorld.RemoveBody(Body);
            Body = null;
        }

        /// <summary>
        /// Gets the Center point for the textures underlying the
        /// Sprite.
        /// </summary>
        /// <returns></returns>
        public Vector2 GetCenter()
        {
            Point temp = GetTypeBoundRect().Center;
            Vector2 cent = new Vector2();
            cent.X = temp.X;
            cent.Y = temp.Y;
            return cent - GetPosition();
        }

        /// <summary>
        /// Returns the bounding rectangle for the textures
        /// underlying this Sprite.
        /// </summary>
        /// <returns></returns>
        private Rectangle GetTypeBoundRect()
        {
            Rectangle rect = new Rectangle();
            rect.X = (int)GetPosition().X;
            rect.Y = (int)GetPosition().Y;
            rect.Width = type.Width;
            rect.Height = type.Height;

            return rect;
        }


        // The following is the implementation of the ITransformable interface
        // = = = = = = = = = = = = =
        //

        public void SetPosition(Vector2 Position)
        {
            if(Body != null)
                this.Body.Position = ConvertUnits.ToSimUnits(Position);
        }

        public Vector2 GetPosition()
        {
            if (Body != null)
                return ConvertUnits.ToDisplayUnits(this.Body.Position);
            else
                return Vector2.Zero;
        }

        public void SetRotation(float rotation)
        {
            this.Body.Rotation = rotation;
        }

        public float GetRotation()
        {
            return this.Body.Rotation;
        }

        public void SetScale(Vector2 newScale)
        {
            this.Scale = newScale;
        }

        public Vector2 GetScale()
        {
            return this.Scale;
        }

        public bool IsActive()
        {
            return true;
        }

        public int PoolIndex
        {
            get;
            set;
        }
    }
}
